#include "MouseHandle.h"

// 引用Windows头文件
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <vector>
#include <algorithm>
#include <chrono>

#include <Core.h>

HHOOK hook_header{nullptr};
static std::vector<PFN_MouseProcess> gMouseCallbacks{};
static std::array<bool, MOUSE_KEY_MAX> gButtonMapping{};
static const double gThresholdTime = 0.25; // 单位s

static void MouseDataConverAndUpdate(MouseData &target, const MSLLHOOKSTRUCT &data, WPARAM btn)
{
    // 更新状态
    //  const uint32_t localBtnIndex = static_cast<uint32_t>(btn - WM_MOUSEFIRST);

    switch (btn)
    {
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
        gButtonMapping[MOUSE_KEY_LBUTTON] = (WM_LBUTTONDOWN == btn);
        target.curKey = MOUSE_KEY_LBUTTON;
        break;
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
        gButtonMapping[MOUSE_KEY_RBUTTON] = (WM_RBUTTONDOWN == btn);
        target.curKey = MOUSE_KEY_RBUTTON;
        break;
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
        gButtonMapping[MOUSE_KEY_MBUTTON] = (WM_MBUTTONDOWN == btn);
        target.curKey = MOUSE_KEY_MBUTTON;
        break;
    case WM_XBUTTONDOWN:
    case WM_XBUTTONUP:
    {
        if (0x10000 == data.mouseData)
        {
            gButtonMapping[MOUSE_KEY_XBUTTON_1] = (WM_XBUTTONDOWN == btn);
            target.curKey = MOUSE_KEY_XBUTTON_1;
        }
        else if(0x20000 == data.mouseData)
        {
            gButtonMapping[MOUSE_KEY_XBUTTON_2] = (WM_XBUTTONDOWN == btn);
            target.curKey = MOUSE_KEY_XBUTTON_2;
        }
        break;
    }
    default:
        target.curKey = MOUSE_KEY_NONE;
        break;
    }

    // 结构体转换
    target.wParam = btn;
    target.pt[0] = data.pt.x;
    target.pt[1] = data.pt.y;
    target.mouseData = data.mouseData;
    target.flags = data.flags;
    target.time = data.time;
    target.dwExtraInfo = data.dwExtraInfo;

    // 处理双击事件
    if (gButtonMapping[target.curKey])
    {
        using namespace std::chrono;
        static std::array<steady_clock::time_point, MOUSE_KEY_MAX> localLastClickTime{};
        static std::array<bool, MOUSE_KEY_MAX> localWaitDBClick{};

        const steady_clock::time_point localCurrentTime = steady_clock::now();
        const duration<double> localDurTime{localCurrentTime - localLastClickTime[target.curKey]};

        if (localDurTime.count() < gThresholdTime && localWaitDBClick[target.curKey])
        {
            localWaitDBClick[target.curKey] = false;
            target.dbclick = TRUE;
        }
        else
        {
            localWaitDBClick[target.curKey] = true;
        }
        localLastClickTime[target.curKey] = localCurrentTime;
    }
}

static LRESULT KeyboardHookProcess(int code, WPARAM w, LPARAM l)
{
    if (WM_MOUSEMOVE != w)
    {
        const MSLLHOOKSTRUCT var = *reinterpret_cast<MSLLHOOKSTRUCT *>(l);
        MouseData localMsData{};
        MouseDataConverAndUpdate(localMsData, var, w);
        for (auto fn : gMouseCallbacks)
        {
            fn(&localMsData);
        }
    }

    return CallNextHookEx(nullptr, code, w, l);
}

void Mouse::AddProc(PFN_MouseProcess fn)
{
    const auto localFinded = std::find(gMouseCallbacks.begin(), gMouseCallbacks.end(), fn);
    if (localFinded != gMouseCallbacks.end())
    {
        return;
    }

    gMouseCallbacks.push_back(fn);
}

void Mouse::DelProc(PFN_MouseProcess fn)
{
    auto s = std::remove(gMouseCallbacks.begin(), gMouseCallbacks.end(), fn);
    (void)(s);
}

bool Mouse::IsButtomDown(const MOUSE_KEY btn)
{
    return gButtonMapping[btn];
}

Mouse::Mouse()
{
    hook_header = SetWindowsHookExW(WH_MOUSE_LL, KeyboardHookProcess, nullptr, 0u);
}

Mouse::~Mouse()
{
    if (nullptr != hook_header)
    {
        UnhookWindowsHookEx(hook_header);
    }
}

Mouse &Mouse::GetInstance()
{
    static Mouse ins{};
    return ins;
}